/**
 * Copyright 2006 Brad Reynolds
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.leeloo.databinding.internal.ui;

import java.io.IOException;
import java.net.ServerSocket;

import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;

/**
 * Server that accepts connections and provides notifications of the received
 * messages.
 * 
 * @author Brad Reynolds
 */
public class TraceServer {
    private boolean running;

    private ServerSocket serverSocket;

    private ClientConnection connection;

    /**
     * ITraceListeners to be notified of trace events. Any access to this object
     * must be synchronized.
     */
    private ListenerList listeners;

    private final Object listenersLock = new Object();

    private static final boolean DEBUG = DataBindingUIPlugin.getDefault().isDebugging()
            && "true".equalsIgnoreCase(Platform.getDebugOption("org.leeloo.databinding.ui/debug")); //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Constructs a new instance.
     */
    public TraceServer() {
    }

    /**
     * Registers a listener to be notified of trace events.
     * <em>Notifications can and will come from threads other than the UI thread.</em>
     * 
     * @param listener
     */
    public void addTraceListener(ITraceListener listener) {
        synchronized (listenersLock) {
            if (listeners == null) {
                listeners = new ListenerList();
            }

            listeners.add(listener);
        }
    }

    /**
     * Removes the listener from the notified listeners.
     * 
     * @param listener
     */
    public void removeTraceListener(ITraceListener listener) {
        synchronized (listenersLock) {
            if (listeners == null) {
                return;
            }

            listeners.remove(listener);
        }
    }

    /**
     * @return <code>true</code> if a client is connected
     */
    public boolean hasConnections() {
        return (connection != null && connection.isConnected());
    }

    /**
     * @return port the server is listening on
     */
    public int getLocalPort() {
        return (isRunning()) ? serverSocket.getLocalPort() : -1;
    }

    /**
     * @return <code>true</code> if the server is running
     */
    public boolean isRunning() {
        return running;
    }

    /**
     * Starts the server on an available port.
     * 
     * @throws IllegalStateException
     *             if already started
     */
    public synchronized void start() {
        if (isRunning()) {
            throw new IllegalStateException("Server is already started."); //$NON-NLS-1$
        }

        try {
            serverSocket = new ServerSocket(0);
            if (DEBUG) {
                System.out.println("TraceServer started on port [" + serverSocket.getLocalPort() + "]."); //$NON-NLS-1$//$NON-NLS-2$
            }

            connection = new ClientConnection(serverSocket, new Callback());
            connection.start();
            running = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Stops the server.
     */
    public synchronized void stop() {
        if (serverSocket != null && !serverSocket.isClosed()) {
            try {
                //Terminate the connection.
                connection.interrupt();
                serverSocket.close();
                
                if (DEBUG) {
                    System.out.println("TraceServer on port [" + serverSocket.getLocalPort() + "] stopped."); //$NON-NLS-1$//$NON-NLS-2$
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        running = false;
    }

    /**
     * Notifies all listeners of a trace message.
     * 
     * @param message
     */
    private void notifyListeners(LogMessage message) {
        synchronized (listenersLock) {
            if (listeners == null) {
                return;
            }

            Object[] theListeners = (Object[]) listeners.getListeners();
            for (int i = 0; i < theListeners.length; i++) {
                ((ITraceListener) theListeners[i]).message(message);
            }
        }
    }

    private class Callback implements ITraceServerCallback {
        public void message(LogMessage message) {
            notifyListeners(message);
        }
    }
}